/**
 * Copyright (c) 2013-2019 Nikita Koksharov
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.redisson;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;

import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.client.RedisResponseTimeoutException;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.misc.TransferListener;

/**
 * Redisson联锁。
 * 允许同时将来自多个不同redis结点的锁看成一个锁，必须这些锁全部上锁成功，才认为联锁上锁成功
 * <p>
 * 联锁的多个锁可以是任意一种锁，公平锁或者非公平锁
 * <p>
 * RLock lock1 = redissonInstance1.getLock("lock1");
 * RLock lock2 = redissonInstance2.getLock("lock2");
 * RLock lock3 = redissonInstance3.getLock("lock3");
 * RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
 * <p>
 * Groups multiple independent locks and manages them as one lock.
 *
 * @author Nikita Koksharov
 */
public class RedissonMultiLock implements RLock {

    /**
     * 定义的子类
     */
    class LockState {

        private final long newLeaseTime;
        private final long lockWaitTime;
        // 保存子锁的列表
        private final List<RLock> acquiredLocks;
        private final long waitTime;
        private final long threadId;
        private final long leaseTime;
        private final TimeUnit unit;

        private long remainTime;
        private long time = System.currentTimeMillis();
        private int failedLocksLimit;

        LockState(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
            this.waitTime = waitTime;
            this.leaseTime = leaseTime;
            this.unit = unit;
            this.threadId = threadId;

            if (leaseTime != -1) {
                if (waitTime == -1) {
                    newLeaseTime = unit.toMillis(leaseTime);
                } else {
                    newLeaseTime = unit.toMillis(waitTime) * 2;
                }
            } else {
                newLeaseTime = -1;
            }

            remainTime = -1;
            if (waitTime != -1) {
                remainTime = unit.toMillis(waitTime);
            }
            lockWaitTime = calcLockWaitTime(remainTime);

            failedLocksLimit = failedLocksLimit();
            acquiredLocks = new ArrayList<>(locks.size());
        }

        void tryAcquireLockAsync(ListIterator<RLock> iterator, RPromise<Boolean> result) {
            if (!iterator.hasNext()) {
                checkLeaseTimeAsync(result);
                return;
            }

            RLock lock = iterator.next();
            RPromise<Boolean> lockAcquiredFuture = new RedissonPromise<Boolean>();
            if (waitTime == -1 && leaseTime == -1) {
                lock.tryLockAsync(threadId)
                        .onComplete(new TransferListener<Boolean>(lockAcquiredFuture));
            } else {
                long awaitTime = Math.min(lockWaitTime, remainTime);
                lock.tryLockAsync(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS, threadId)
                        .onComplete(new TransferListener<Boolean>(lockAcquiredFuture));
            }

            lockAcquiredFuture.onComplete((res, e) -> {
                boolean lockAcquired = false;
                if (res != null) {
                    lockAcquired = res;
                }

                if (e instanceof RedisResponseTimeoutException) {
                    unlockInnerAsync(Arrays.asList(lock), threadId);
                }

                if (lockAcquired) {
                    acquiredLocks.add(lock);
                } else {
                    if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                        checkLeaseTimeAsync(result);
                        return;
                    }

                    if (failedLocksLimit == 0) {
                        unlockInnerAsync(acquiredLocks, threadId).onComplete((r, ex) -> {
                            if (ex != null) {
                                result.tryFailure(ex);
                                return;
                            }

                            if (waitTime == -1) {
                                result.trySuccess(false);
                                return;
                            }

                            failedLocksLimit = failedLocksLimit();
                            acquiredLocks.clear();
                            // reset iterator
                            while (iterator.hasPrevious()) {
                                iterator.previous();
                            }

                            checkRemainTimeAsync(iterator, result);
                        });
                        return;
                    } else {
                        failedLocksLimit--;
                    }
                }

                checkRemainTimeAsync(iterator, result);
            });
        }

        private void checkLeaseTimeAsync(RPromise<Boolean> result) {
            if (leaseTime != -1) {
                AtomicInteger counter = new AtomicInteger(acquiredLocks.size());
                for (RLock rLock : acquiredLocks) {
                    RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
                    future.onComplete((res, e) -> {
                        if (e != null) {
                            result.tryFailure(e);
                            return;
                        }

                        if (counter.decrementAndGet() == 0) {
                            result.trySuccess(true);
                        }
                    });
                }
                return;
            }

            result.trySuccess(true);
        }

        private void checkRemainTimeAsync(ListIterator<RLock> iterator, RPromise<Boolean> result) {
            if (remainTime != -1) {
                remainTime += -(System.currentTimeMillis() - time);
                time = System.currentTimeMillis();
                if (remainTime <= 0) {
                    unlockInnerAsync(acquiredLocks, threadId).onComplete((res, e) -> {
                        if (e != null) {
                            result.tryFailure(e);
                            return;
                        }

                        result.trySuccess(false);
                    });
                    return;
                }
            }

            tryAcquireLockAsync(iterator, result);
        }

    }

    // 保存内部子锁
    final List<RLock> locks = new ArrayList<>();

    /**
     * 联锁的构造函数。接收多个RLock实例
     * 多个RLock实例可以来自多个不同的RedissonClient实例，即来自不同的redis结点的锁
     *
     * @param locks - array of locks
     */
    public RedissonMultiLock(RLock... locks) {
        if (locks.length == 0) {
            throw new IllegalArgumentException("Lock objects are not defined");
        }
        // 构造时就是将所有的RLock添加到子锁列表中
        this.locks.addAll(Arrays.asList(locks));
    }

    /**
     * 联锁的lock()，总是可响应中断的，因为联锁在获取子锁时，子锁是可能抛出异常的。
     * lock()底层调用各个子锁的tryLock()获取锁，只要不是所有子锁都上锁成功，则循环调用子锁的tryLock()
     */
    @Override
    public void lock() {
        try {
            lockInterruptibly();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        try {
            lockInterruptibly(leaseTime, unit);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public RFuture<Void> lockAsync(long leaseTime, TimeUnit unit) {
        return lockAsync(leaseTime, unit, Thread.currentThread().getId());
    }

    @Override
    public RFuture<Void> lockAsync(long leaseTime, TimeUnit unit, long threadId) {
        long baseWaitTime = locks.size() * 1500;
        long waitTime = -1;
        if (leaseTime == -1) {
            waitTime = baseWaitTime;
        } else {
            leaseTime = unit.toMillis(leaseTime);
            waitTime = leaseTime;
            if (waitTime <= 2000) {
                waitTime = 2000;
            } else if (waitTime <= baseWaitTime) {
                waitTime = ThreadLocalRandom.current().nextLong(waitTime / 2, waitTime);
            } else {
                waitTime = ThreadLocalRandom.current().nextLong(baseWaitTime, waitTime);
            }
        }

        RPromise<Void> result = new RedissonPromise<Void>();
        tryLockAsync(threadId, leaseTime, TimeUnit.MILLISECONDS, waitTime, result);
        return result;
    }

    protected void tryLockAsync(long threadId, long leaseTime, TimeUnit unit, long waitTime, RPromise<Void> result) {
        tryLockAsync(waitTime, leaseTime, unit, threadId).onComplete((res, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }

            if (res) {
                result.trySuccess(null);
            } else {
                tryLockAsync(threadId, leaseTime, unit, waitTime, result);
            }
        });
    }


    @Override
    public void lockInterruptibly() throws InterruptedException {
        lockInterruptibly(-1, null);
    }

    /**
     * 联锁的lock()方法会维护一个超时时间属性waitTime，对所有的子锁调用tryLock(waitTime)进行上锁
     * @param leaseTime 所有子锁共同的过期时间。如果永不过期则为-1(即看门狗接管)
     * @param unit
     * @throws InterruptedException
     */
    @Override
    public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
        // 定义一个基础超时时间。使用子锁的个数*1.5秒作为基础超时时间
        long baseWaitTime = locks.size() * 1500L;
        // waitTime是联锁真正的超时时间。指的是在获取锁时，超过多久后仍子锁仍无法全部上锁，则失败
        long waitTime = -1;
        if (leaseTime == -1) {
            waitTime = baseWaitTime;
        } else {
            leaseTime = unit.toMillis(leaseTime);
            waitTime = leaseTime;
            if (waitTime <= 2000) {
                waitTime = 2000;
            } else if (waitTime <= baseWaitTime) {
                // releaseTime过长时，waitTime会在一定的范围内取随机值
                waitTime = ThreadLocalRandom.current().nextLong(waitTime / 2, waitTime);
            } else {
                waitTime = ThreadLocalRandom.current().nextLong(baseWaitTime, waitTime);
            }
        }

        // 调用自身的tryLock()对所有子锁进行上锁。只有所有子锁都上锁成功，tryLock()放回true，否则返回false
        // 由于此处是联锁lock()的实现，因此只要false就会循环重试
        while (true) {
            if (tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS)) {
                return;
            }
        }
    }

    /**
     * 联锁tryLock()，只尝试获取锁一次，成功与否均立刻返回
     * @return
     */
    @Override
    public boolean tryLock() {
        try {
            return tryLock(-1, -1, null);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    protected void unlockInner(Collection<RLock> locks) {
        List<RFuture<Void>> futures = new ArrayList<>(locks.size());
        for (RLock lock : locks) {
            futures.add(lock.unlockAsync());
        }

        for (RFuture<Void> unlockFuture : futures) {
            unlockFuture.awaitUninterruptibly();
        }
    }

    protected RFuture<Void> unlockInnerAsync(Collection<RLock> locks, long threadId) {
        if (locks.isEmpty()) {
            return RedissonPromise.newSucceededFuture(null);
        }

        RPromise<Void> result = new RedissonPromise<Void>();
        AtomicInteger counter = new AtomicInteger(locks.size());
        for (RLock lock : locks) {
            lock.unlockAsync(threadId).onComplete((res, e) -> {
                if (e != null) {
                    result.tryFailure(e);
                    return;
                }

                if (counter.decrementAndGet() == 0) {
                    result.trySuccess(null);
                }
            });
        }
        return result;
    }

    /**
     * 联锁的tryLock()方法，带有try超时时间
     * @param waitTime
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
        return tryLock(waitTime, -1, unit);
    }

    /**
     * 允许子锁获取失败的数量上限。
     * 联锁本身要求所有子锁获取成功，因此返回0.
     * RedLock()覆写该方法实现多数成功
     */
    protected int failedLocksLimit() {
        return 0;
    }

    /**
     * 联锁的加锁核心。不管是联锁的阻塞lock()还是非阻塞tryLock()底层都是用该方法
     *
     * @param waitTime  联锁最终都是在调用子锁的tryLock()方法。waitTime就是子锁tryLock()的try超时时间。
     *                  -1代表只尝试一次，成功与否立刻返回。其他值代表try超时时间
     * @param leaseTime lease time 获取到子锁后的子锁ttl
     * @param unit      time unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        // 定义了一个newLeaseTime，比真正的子锁leaseTime要小。
        // 在尝试上锁阶段都是用 newLeaseTime去为获取到的锁上ttl
        // 目的是防止联锁lock()时联锁所在的服务器宕掉了，这样可以让无法手动解锁的子锁尽可能快的自动释放
        // 在联锁获取了所有子锁的锁后，再手动将他们的ttl修改为真正的leaseTime
        long newLeaseTime = -1;
        if (leaseTime != -1) {
            if (waitTime == -1) {
                newLeaseTime = unit.toMillis(leaseTime);
            } else {
                newLeaseTime = unit.toMillis(waitTime) * 2;
            }
        }

        long time = System.currentTimeMillis();
        long remainTime = -1;
        if (waitTime != -1) {
            remainTime = unit.toMillis(waitTime);
        }
        long lockWaitTime = calcLockWaitTime(remainTime);

        // 某个版本之后将错误上限改为0，即联锁要求所有子锁必须全部成功。
        // 带有非零错误上限的功能由 RedLock实现
        int failedLocksLimit = failedLocksLimit();
        // acquiredLocks保存成功上锁的子锁
        List<RLock> acquiredLocks = new ArrayList<>(locks.size());

        // 遍历子锁列表上锁。
        // 注意采用串行方法上锁。目的可能是只要有一个子锁失败，之后的子锁直接不再尝试
        for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext(); ) {
            RLock lock = iterator.next();
            boolean lockAcquired;

            // 看情况调用子锁的tryLock()还是tryLock(waitTime)
            try {
                if (waitTime == -1 && leaseTime == -1) {
                    lockAcquired = lock.tryLock();
                } else {
                    long awaitTime = Math.min(lockWaitTime, remainTime);
                    // 注意获取子锁时ttl设的都是newLeaseTime
                    lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
                }
            } catch (RedisResponseTimeoutException e) {
                // 某个子锁抛出异常，则强制将该子锁解锁以防意外
                unlockInner(Arrays.asList(lock));
                lockAcquired = false;
            } catch (Exception e) {
                lockAcquired = false;
            }

            // 判断当前子锁是否加锁成功，成功则添加到成功列表中
            if (lockAcquired) {
                acquiredLocks.add(lock);
            } else {
                // 当已经成功获取的子锁的数量满足failedLocksLimit的限制时，剩下的直接不获取了，就当全失败了此时的联锁也获取成功了。
                if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                    break;
                }

                // 判断failedLocksLimit是否到0，如果不是0则允许本次失败，否则就是到达失败次数上限
                if (failedLocksLimit == 0) {
                    // 将已经获取到的子锁全部释放
                    unlockInner(acquiredLocks);
                    // 如果联锁调用的是tryLockOnce()，那么联锁加锁失败。
                    if (waitTime == -1) {
                        return false;
                    }
                    // 否则重新开始获取锁，重新获得failedLocksLimit，清空acquiredLocks，iterator回到首个子锁
                    failedLocksLimit = failedLocksLimit();
                    acquiredLocks.clear();
                    // reset iterator
                    while (iterator.hasPrevious()) {
                        iterator.previous();
                    }
                } else {
                    failedLocksLimit--;
                }
            }

            if (remainTime != -1) {
                remainTime -= System.currentTimeMillis() - time;
                time = System.currentTimeMillis();
                if (remainTime <= 0) {
                    unlockInner(acquiredLocks);
                    return false;
                }
            }
        }

        // 在所有的子锁都获取成功了，在手动将他们的ttl更改为真正的leaseTime
        // P.S. 那要是这个时候又宕机了怎么办?那是真没办法，只能等ttl后自动过期了。程序总是有舍有得的。
        if (leaseTime != -1) {
            List<RFuture<Boolean>> futures = new ArrayList<>(acquiredLocks.size());
            for (RLock rLock : acquiredLocks) {
                RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
                futures.add(future);
            }

            for (RFuture<Boolean> rFuture : futures) {
                rFuture.syncUninterruptibly();
            }
        }

        return true;
    }

    @Override
    public RFuture<Boolean> tryLockAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RPromise<Boolean> result = new RedissonPromise<Boolean>();
        LockState state = new LockState(waitTime, leaseTime, unit, threadId);
        state.tryAcquireLockAsync(locks.listIterator(), result);
        return result;
    }

    @Override
    public RFuture<Boolean> tryLockAsync(long waitTime, long leaseTime, TimeUnit unit) {
        return tryLockAsync(waitTime, leaseTime, unit, Thread.currentThread().getId());
    }


    protected long calcLockWaitTime(long remainTime) {
        return remainTime;
    }

    @Override
    public RFuture<Void> unlockAsync(long threadId) {
        return unlockInnerAsync(locks, threadId);
    }

    @Override
    public void unlock() {
        List<RFuture<Void>> futures = new ArrayList<>(locks.size());

        for (RLock lock : locks) {
            futures.add(lock.unlockAsync());
        }

        for (RFuture<Void> future : futures) {
            future.syncUninterruptibly();
        }
    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }

    @Override
    public RFuture<Boolean> forceUnlockAsync() {
        throw new UnsupportedOperationException();
    }

    @Override
    public RFuture<Void> unlockAsync() {
        return unlockAsync(Thread.currentThread().getId());
    }

    @Override
    public RFuture<Boolean> tryLockAsync() {
        return tryLockAsync(Thread.currentThread().getId());
    }

    @Override
    public RFuture<Void> lockAsync() {
        return lockAsync(Thread.currentThread().getId());
    }

    @Override
    public RFuture<Void> lockAsync(long threadId) {
        return lockAsync(-1, null, threadId);
    }

    @Override
    public RFuture<Boolean> tryLockAsync(long threadId) {
        return tryLockAsync(-1, -1, null, threadId);
    }

    @Override
    public RFuture<Boolean> tryLockAsync(long waitTime, TimeUnit unit) {
        return tryLockAsync(waitTime, -1, unit);
    }

    @Override
    public RFuture<Integer> getHoldCountAsync() {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getName() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean forceUnlock() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isLocked() {
        throw new UnsupportedOperationException();
    }

    @Override
    public RFuture<Boolean> isLockedAsync() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isHeldByThread(long threadId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isHeldByCurrentThread() {
        throw new UnsupportedOperationException();
    }

    @Override
    public int getHoldCount() {
        throw new UnsupportedOperationException();
    }

    @Override
    public RFuture<Long> remainTimeToLiveAsync() {
        throw new UnsupportedOperationException();
    }

    @Override
    public long remainTimeToLive() {
        throw new UnsupportedOperationException();
    }

}
